/**
 * Copyright 2011 Honeywell Federal Manufacturing & Technologies, LLC. This material was produced under U.S. Government
 * contract DE-ACO4-01AL66850, Honeywell Federal Manufacturing & Technologies, LLC, (FM&T) with the U. S. Department of
 * Energy for the operation of the Kansas City Plant . The U.S. Government has rights to use, reproduce, and distribute
 * this software.  NEITHER THE GOVERNMENT NOR Honeywell Federal Manufacturing & Technologies, LLC, MAKES ANY WARRANTY,
 * EXPRESS OR IMPLIED, OR ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE.  If software is modified to produce
 * derivative works, such modified software should be clearly marked, so as not to confuse it with the version available
 * from FM&T.
 *
 * Additionally, this program is free software; you can redistribute it and/or modify it under the terms of the GNU
 * General Public License Version 3 published by the Free Software Foundation. Accordingly, this program is distributed in
 * the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this library.
 * If not, see http://www.gnu.org/licenses/.
 */
package org.braintrain.tapestry.view;

import com.javaforge.tapestry.spring.annotations.InjectSpring;
import org.apache.commons.lang.StringUtils;
import org.apache.tapestry.IAsset;
import org.apache.tapestry.IExternalPage;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.annotations.Asset;
import org.apache.tapestry.annotations.Bean;
import org.apache.tapestry.annotations.Persist;
import org.apache.tapestry.request.IUploadFile;
import org.apache.tapestry.valid.ValidationDelegate;
import org.braintrain.domain.Media;
import org.braintrain.domain.MediaDirectory;
import org.braintrain.domain.ResourceFacade;
import org.braintrain.domain.types.MediaFileType;
import org.braintrain.domain.types.ResourceType;
import org.braintrain.services.DeltaResourceManager;
import org.braintrain.services.MediaService;
import org.braintrain.services.search.media.RootMediaCriteriaFactory;
import org.braintrain.services.search.media.RootMediaDirectoryCriteriaFactory;
import org.braintrain.services.search.navigation.NavigationInProductionUsesMediaCriteriaFactory;

import java.util.Collection;

/**
 * Tapestry File Browser page.  This page is used by the TinyMCE plugin as a content
 * link browser.  The page itself does much more though... It manages the directory
 * structure and content of the file repository.
 * 
 * @author John Ericksen
 *
 */

public abstract class FileBrowser extends BrainTrainSecuredBasePage implements IExternalPage{

    //todo: persist current selection?

    private final static String unallowedFolderCharacters = "[^A-Za-z0-9]";

    @InjectSpring("mediaService")
    public abstract MediaService getMediaService();

    @InjectSpring("resourceManager")
    public abstract DeltaResourceManager getResourceManager();
    
    @Bean
    public abstract ValidationDelegate getDefaultDelegate();
    
    @Asset("images/folder.gif")
    public abstract IAsset getFolderImage();
    @Asset("images/addfolder.gif")
    public abstract IAsset getAddFolderImage();
    
    @Asset("images/close.gif")
    public abstract IAsset getDeleteImage();
    @Asset("images/edit.gif")
    public abstract IAsset getRenameImage();
    @Asset("images/undo.gif")
    public abstract IAsset getUndeleteImage();
    @Asset("images/move.gif")
    public abstract IAsset getMoveImage();
    @Asset("images/moveto.gif")
    public abstract IAsset getMoveToImage();
    @Asset("images/magnifier.gif")
    public abstract IAsset getPreviewImage();
    
    //  loop variables
    public abstract MediaDirectory getDirectory();
    public abstract void setDirectory(MediaDirectory directory);
    
    public abstract Media getMedia();
    public abstract void setMedia(Media directory);
    
    public abstract IUploadFile getUploadFile();
    public abstract void setUploadFile(IUploadFile directory);
    
    public abstract String getFileName();
    public abstract void setFileName(String name);
    
    public abstract boolean getOverwrite();
    public abstract void setOverwrite(boolean o);
    
    /**
     * Image Asset Switch method.  Determines which file icon to use with a given
     * media type
     * @return icon filename
     */
    public String getIconFile()
    {
        return getMedia().getFileType().getIconFile();
    }

    public void activateExternalPage(Object[] parameters, IRequestCycle cycle) {
        //empty
        
    }

    /**
     * Returns the Current Directory Id from the File Browswer ASO
     * @return
     */
    @Persist
    public abstract Long getCurrentDirectoryId() ;
    public abstract void setCurrentDirectoryId(Long id);

    @Persist
    public abstract Media getMoveMedia();
    public abstract void setMoveMedia(Media media);
    
    /**
     * Resets the overwrite value to false after any file browser loads
     */
    protected void finishLoad() {
        setOverwrite(false);
    }
    
    public boolean isMediaReferenced() {
        return getMedia().getResource().getResourceReferences().size() > 0;
    }
    
    /**
     * Returns the current directory object given from the getCurrentDirectoryId() method
     * 
     * @return
     */
    public MediaDirectory getCurrentDirectory() {
        if(getCurrentDirectoryId() != null) {
            return getGenericService().getById(MediaDirectory.class, getCurrentDirectoryId());
        }
        //if anything go back to the root (null directory)
        return null;
    }
    
    /**
     * Returns all the children directories of the current directory.  Of course, if the directory is null,
     * return all the root directies (where parent = null, see RootMediaDirectoryCriteriaFactory)
     * @return
     */
    @SuppressWarnings("unchecked")
    public Collection<MediaDirectory> getDirectoryFolders() {
        if(getCurrentDirectoryId() == null) {
            return getGenericService().find(new RootMediaDirectoryCriteriaFactory());
        }
        return getCurrentDirectory().getChildren();
    }
    
    /**
     * Returns all the children media of the current directory.  Of course, if the directory is null,
     * return all the root media (where parent = null, see RootMediaCriteriaFactory)
     * @return
     */
    public Collection<Media> getDirectoryMedia() {
        Collection<Media> dirMedia;
        if(getCurrentDirectoryId() == null) {
            dirMedia = getGenericService().find(new RootMediaCriteriaFactory());
        }
        else {
            dirMedia = getCurrentDirectory().getFiles();
        }
        return dirMedia;
    }
    
    /**
     * Change directory.
     * 
     * @param cycle
     */
    public void cd(IRequestCycle cycle, Long currentDirectoryId) {
        setCurrentDirectoryId(currentDirectoryId);
    }
    
    /**
     * Returns the current path in Unix style. (see MediaDirectory.getPath())
     * 
     * @return
     */
    public String getCurrentPath() {
        if(getCurrentDirectoryId() == null) {
            return "/";
        }
        else {
            MediaDirectory dir = getCurrentDirectory();
            return dir.getPath();
        }
    }
    
    /**
     * Determines the original path extension (windows style extensions) and fixes the
     * original file name with the correct extension (if needed)
     * 
     * @param fileName
     * @param original
     * @return
     */
    private String copyOverwriteExtension(String fileName, String original)
    {
        int fileNameExtensionIndex = original.lastIndexOf('.');
        String fixedFileName = fileName;
        
        if(fileNameExtensionIndex != -1) {
            String extension = original.substring(fileNameExtensionIndex);

            int nameExtensionIndex = fixedFileName.lastIndexOf('.');
            
            if(nameExtensionIndex != -1) {
                fixedFileName = fixedFileName.substring(0, nameExtensionIndex);
            }
            fixedFileName = fixedFileName + extension;
        }
        
        return fixedFileName;
    }

    private String cleanFileName(String inputFileName){
        String fileName = inputFileName;
        if(inputFileName.contains("\\")) {
            fileName = inputFileName.substring(inputFileName.lastIndexOf('\\'));
        }
        return StringUtils.trim(fileName);
    }

    private String cleanDirectoryName(String inputFileName){
        String fileName = inputFileName.replace(unallowedFolderCharacters, "");
        if(inputFileName.contains("\\")) {
            fileName = inputFileName.substring(inputFileName.lastIndexOf('\\'));
        }
        return StringUtils.trim(fileName);
    }

    /**
     * Uploads the given file and checks/fixes the appropriate values for correctness:
     * 1. Supplied/Original file name
     * 2. File size
     * 3. File type
     * 
     * @param cycle
     */
    public void uploadSubmit(IRequestCycle cycle)
    {
        //file should have a size
        if(getUploadFile() != null && getUploadFile().getSize() > 0)
        {
            String name = getFileName();

            //if no filename supplied use the uploadFile's name
            if(StringUtils.isBlank(name)) {
                name = getUploadFile().getFileName();
            }

            String fileName = cleanFileName(name);

            //make sure the file type is valid
            if(!MediaFileType.getTypes().matcher(fileName.toLowerCase()).find()) {
                getDefaultDelegate().record(null, "Uploaded file must be of the following types: \n" + MediaFileType.getTypesString());
                return;
            }
            //determine if there is a duplicate file name
            Media media = getMediaService().getUniqueMedia(getCurrentDirectoryId(), fileName);

            boolean mediaInProductionUse = false;
            if(media != null && getOverwrite()) {
                mediaInProductionUse = getGenericService().find(new NavigationInProductionUsesMediaCriteriaFactory(media)).size() > 0;
            }

            if(media == null || (getOverwrite() && !mediaInProductionUse)) {
                if(media == null) {
                    media = new Media();
                }

                media.setName(fileName);
                media.setPath(getCurrentPath());
                media.setFileType(MediaFileType.getFileType(name));

                media.setParent(getCurrentDirectory());
                if(getCurrentDirectoryId() != null) {
                    getCurrentDirectory().getFiles().add(media);
                }

                if(media.getResource() == null){
                    media.setResource(new ResourceFacade(ResourceType.Media));
                    media.getResource().setStep(0L);
                    media.getResource().setResourceHandle(name);
                    media.getResource().setOrganization(media.getPath());
                }

                //set data

                getResourceManager().saveResourceValue(getUploadFile().getStream(), media.getResource());

                getGenericService().saveOrUpdate(media);
            }
            else if(mediaInProductionUse) {
                getDefaultDelegate().record(null, "Uploaded file already exists under the name '" + name + "' and is in production user.  Please rename and upload again.");
            }
            else {
                getDefaultDelegate().record(null, "Uploaded file already exists under the name '" + name + "'.  Please rename and upload again.");
            }
        }
    }
    
    /**
     * Adds a folder to the current directory.  Folder name is in params[params.length-1]
     * 
     * @param cycle
     */

    public void addFolder(IRequestCycle cycle, String inputName) {
        addFolder(cycle, null, inputName);
    }
    
    public void addFolder(IRequestCycle cycle, Long currentDirectoryId, String inputName) {
        setCurrentDirectoryId(currentDirectoryId);

        String name = cleanDirectoryName(inputName);

        //make sure the folder does not exist already.. and create
        if(!StringUtils.isBlank(name) &&
            getMediaService().getUniqueFileManageable(getCurrentDirectoryId(), name)) {
            
            MediaDirectory directory = new MediaDirectory();
            directory.setName(name);

            directory.setParent(getCurrentDirectory());
            if(getCurrentDirectoryId() != null) {
                getCurrentDirectory().getChildren().add(directory);
            }
            getGenericService().saveOrUpdate(directory);
        }
    }
    
    /**
     * Delete a given media with id = params[0]
     * 
     * @param cycle
     */
    public void deleteMedia(IRequestCycle cycle, Long directoryId, Long mediaId) {
        setCurrentDirectoryId(directoryId);

        Media toDelete = getGenericService().getById(Media.class, mediaId);

        if(toDelete.getResource().getResourceReferences() == null || toDelete.getResource().getResourceReferences().size() == 0) {
            getGenericService().delete(toDelete);
        }
        else {
            getDefaultDelegate().record(null, "File to be deleted must not be referenced by any page");
        }
    }
    
    /**
     * Delete a given folder with id = params[0]
     * 
     * @param cycle
     */
    public void deleteFolder(IRequestCycle cycle, Long currentDirectoryId, Long mediaId) {
        setCurrentDirectoryId(currentDirectoryId);

        MediaDirectory toDelete = getGenericService().getById(MediaDirectory.class, mediaId);

        if((toDelete.getFiles() == null || toDelete.getFiles().size() == 0) && (toDelete.getChildren() == null || toDelete.getChildren().size() == 0)) {
            if(getCurrentDirectoryId() != null) {
                getCurrentDirectory().getChildren().remove(toDelete);
            }
            getGenericService().delete(toDelete);
        }
        else {
            getDefaultDelegate().record(null, "Directory to be deleted must be empty");
        }
    }
    
    /**
     * Determines if there is a media to move.
     * @return
     */
    public boolean moveMediaDropBoxFull() {
        return getMoveMedia() != null;
    }
    
    /**
     * Associate which media to move.
     * 
     * @param cycle
     */
    public void startMoveMedia(IRequestCycle cycle, Long currentDirectoryId, Long mediaId) {
        setCurrentDirectoryId(currentDirectoryId);

        Media media = getGenericService().getById(Media.class, mediaId);

        setMoveMedia(media);
    }
    
    /**
     * Move the associated media to the current directory
     * @param cycle
     */
    public void moveMedia(IRequestCycle cycle, Long currentDirectoryId)
    {
        setCurrentDirectoryId(currentDirectoryId);
             
        Media media = (Media)getGenericService().getById(Media.class, getMoveMedia().getId());

        if(media != null && (media.getResource().getResourceReferences() == null || media.getResource().getResourceReferences().size() == 0)) {
            if(media.getParent() != null) {
                media.getParent().getFiles().remove(media);
            }
            if(getCurrentDirectoryId() != null) {
                getCurrentDirectory().getFiles().add(media);
                media.setParent(getCurrentDirectory());
            }
            else {
                media.setParent(null);
            }

            media.setPath(getCurrentPath());

            getGenericService().saveOrUpdate(media);
        }
        else {
            getDefaultDelegate().record(null, "File to move is either referenced or no longer available");
        }

        setMoveMedia(null);
    }

    /**
     * Rename a given media to params[params.length-1]
     * @param cycle
     */
    public void renameMedia(IRequestCycle cycle, Long currentDirectoryId, Long mediaId, String nameInput) {

        setCurrentDirectoryId(currentDirectoryId);

        Media media = getGenericService().getById(Media.class, mediaId);

        if(media != null) {
            String name = copyOverwriteExtension(cleanFileName(nameInput), media.getName());

            if(name.length() > 0 &&
                getMediaService().getUniqueFileManageable(getCurrentDirectoryId(), name)) {

                media.setName(name);
                getGenericService().saveOrUpdate(media);
            }
        }
    }
    
    /**
     * Rename a given directory to params[params.length-1]
     * @param cycle
     */
    public void renameDirectory(IRequestCycle cycle, Long directoryId, Long directoryToRenameId, String nameInput) {
        setCurrentDirectoryId(directoryId);

        MediaDirectory dir =  getGenericService().getById(MediaDirectory.class, directoryToRenameId);

        if(dir != null){

            String name = cleanDirectoryName(nameInput);

            if(name.length() > 0) {

                if(getMediaService().getUniqueFileManageable(getCurrentDirectoryId(), name)) {
                    dir.setName(name);

                    getGenericService().saveOrUpdate(dir);
                }
            }
        }
    }
    
    /**
     * Concatenates a long string to end characters length and adds ".." to the end
     * 
     * @param in
     * @param end
     * @return
     */
    public String dotdot(String in, int end) {
        if(in.length() > end) {
            return in.substring(0, end) + "..";
        }
        return in;
    }
}
